using System;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace Krilib.Data
{
    /// <summary>
    /// Search data base
    /// </summary>
    /// <remarks>
    /// For XMLSerializer support,this is not interface but abstract class.
    /// </remarks>
    public abstract class SearchDataBase : IXmlSerializable, ICloneable
    {
        public abstract bool IsMatched(Data.Status status);

        public abstract System.Xml.Schema.XmlSchema GetSchema();

        public abstract void ReadXml(System.Xml.XmlReader reader);

        public abstract void WriteXml(System.Xml.XmlWriter writer);

        public abstract object Clone();
    }

    /// <summary>
    /// Search data cluster
    /// </summary>
    public sealed class SearchDataCluster : SearchDataBase, IXmlSerializable
    {
        /// <summary>
        /// Impling search datas
        /// </summary>
        public SearchDataBase[] SearchDatas;

        /// <summary>
        /// SearchDataCluster constructor
        /// </summary>
        /// <param name="sdbarray">initial array</param>
        public SearchDataCluster()
        {
            SearchDatas = null;
        }

        /// <summary>
        /// SearchDataCluster constructor
        /// </summary>
        /// <param name="sdbarray">initial array</param>
        public SearchDataCluster(SearchDataBase[] sdbarray)
        {
            SearchDatas = sdbarray;
        }

        /// <summary>
        /// Uses or matching
        /// </summary>
        public bool UseOrMatch = false;

        /// <summary>
        /// Check match status to this search data
        /// </summary>
        /// <param name="status">status data</param>
        /// <returns>Match:true</returns>
        public override bool IsMatched(Status status)
        {
            if (SearchDatas != null)
            {
                foreach (SearchDataBase sdb in SearchDatas)
                {
                    if (sdb.IsMatched(status) == UseOrMatch)
                    {
                        return UseOrMatch;
                    }
                }
            }
            return !UseOrMatch;
        }

        public override string ToString()
        {
            if (SearchDatas == null || SearchDatas.Length == 0)
                return "Cluster: 0 item(s).";
            else
                return "Cluster: " + SearchDatas.Length.ToString() + " item(s).";
        }

        /// <summary>
        /// Get xml schema for XMLSerializer.
        /// </summary>
        /// <returns></returns>
        public override System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Read xml for XMLSerializer.
        /// </summary>
        /// <param name="reader">xmlreader</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            List<SearchDataBase> sdbarray = new List<SearchDataBase>();

            //Current Position :: <SearchDataCluster>
            reader.ReadToDescendant("data");
            //Current Position :: <data>

            //if reader isn't in "data",it may be in ending data.
            while (reader.Name == "data")
            {
                string attr = reader.GetAttribute("type");
                if (attr == "CLUSTER")
                {
                    reader.ReadToDescendant("SearchDataCluster");
                    XmlSerializer serz = new XmlSerializer(typeof(SearchDataCluster),
                        new XmlRootAttribute("SearchDataCluster"));
                    sdbarray.Add((SearchDataCluster)serz.Deserialize(reader));
                }
                else if (attr != null && attr.StartsWith("SD_"))
                {
                    try
                    {
                        Type targetType = Type.GetType(attr.Substring(3));
                        reader.ReadToDescendant(targetType.Name);
                        XmlSerializer serz = new XmlSerializer(targetType);
                        var newSdb = serz.Deserialize(reader) as SearchDataBase;
                        if (newSdb != null)
                            sdbarray.Add(newSdb);
                        else
                            Bridge.DebugReporter.AddReport("SearchDataCluster Reflection Reader -> Cast Error:" + attr);
                    }
                    catch (Exception e)
                    {
                        Bridge.DebugReporter.AddReport(e);
                    }
                }
                else if (attr != null && attr != String.Empty)
                {
                    //Invalid operation
                    Bridge.DebugReporter.AddReport("SearchDataCluster Reflection Reader -> Undefined Attribute:" + attr);
                }
                //prohibit null
                reader.ReadToNextSibling("data");
            }
            SearchDatas = sdbarray.ToArray();
            reader.Read();
        }

        /// <summary>
        /// Get xml for XMLSerializer.
        /// </summary>
        /// <param name="writer">xmlwriter</param>
        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            if (SearchDatas == null || SearchDatas.Length == 0)
                return;
            foreach (SearchDataBase sdb in SearchDatas)
            {
                if (sdb == null) continue;

                writer.WriteStartElement("data");
                if (sdb is SearchDataCluster)
                {
                    writer.WriteAttributeString("type", "CLUSTER");
                    XmlSerializer serz = new XmlSerializer(typeof(SearchDataCluster));
                    serz.Serialize(writer, sdb);
                }
                else if (sdb is SearchData)
                {
                    writer.WriteAttributeString("type", "SD_" + sdb.GetType().AssemblyQualifiedName);
                    XmlSerializer serz = new XmlSerializer(sdb.GetType());
                    serz.Serialize(writer, sdb);
                }
                else
                {
                    //Unknown Data type
                    writer.WriteAttributeString("type", "INVALID");
                    Bridge.DebugReporter.AddReport(
                        new Krilib.Bridges.DebugReport(
                            "Type data not found\n >" + sdb.ToString(),
                            Krilib.Bridges.DebugReport.Priorities.Warn));
                }
                writer.WriteEndElement();
            }
        }

        public override object Clone()
        {
            var newsdc = new SearchDataCluster();

            newsdc.UseOrMatch = this.UseOrMatch;

            var sdl = new List<SearchDataBase>();
            foreach (var sd in this.SearchDatas)
                sdl.Add((SearchDataBase)sd.Clone());
            newsdc.SearchDatas = sdl.ToArray();

            return newsdc;
        }
    }

    /// <summary>
    /// Search data
    /// </summary>
    public abstract class SearchData : SearchDataBase
    {
        /// <summary>
        /// Searchdata identification 
        /// </summary>
        public abstract string Identification { get; }

        /// <summary>
        /// Check match this search data
        /// </summary>
        /// <param name="status">status</param>
        /// <returns>match:true</returns>
        public override abstract bool IsMatched(Data.Status status);

        /// <summary>
        /// Describe this search data
        /// </summary>
        public abstract string Description { get; }

        /// <summary>
        /// Name of this search data
        /// </summary>
        public abstract string Name { get; }

        /// <summary>
        /// Search Data Argument Set User Interface Control
        /// </summary>
        /// <remarks>
        /// If you don't need any argument,you may return null.
        /// </remarks>
        public virtual Control.SDataArgSetUI GetArgSetUI()
        {
            return null;
        }

        /// <summary>
        /// Exclude flag
        /// </summary>
        public abstract bool Exclude { get;set;}

        /// <summary>
        /// Get Description
        /// </summary>
        public sealed override string ToString()
        {
            if (Exclude)
                return "NOT:" + this.Description;
            else
                return this.Description;
        }

        /// <summary>
        /// Get xml schema for XMLSerializer.
        /// </summary>
        /// <returns></returns>
        public override System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Read xml for XMLSerializer.
        /// </summary>
        /// <remarks>
        /// If you override this prop,you should call base.WriteXml() after your opr.
        /// </remarks>
        /// <param name="reader">xmlreader</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            Exclude = bool.Parse(reader.ReadElementString("Exclude"));
        }

        /// <summary>
        /// Get xml for XMLSerializer.
        /// </summary>
        /// <remarks>
        /// If you override this prop,you should call base.WriteXml() after your opr.
        /// </remarks>
        /// <param name="writer">xmlwriter</param>
        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteElementString("Exclude", Exclude.ToString());
        }
    }
}
